library(Seurat)
library(ggplot2)
library(pheatmap)
library(umap)
library(dplyr)
library(GGally)
library(ggExtra)
library(clustree)
library(RColorBrewer)
getPalette = colorRampPalette(brewer.pal(9, "Set1"))
citeCells_qc = readRDS("data/citeCells_qc.Rds")

multi-level clustering on RNA data

if (any(grepl("SCT_snn_res.",colnames(citeCells_qc@meta.data)))){
  citeCells_qc@meta.data = citeCells_qc@meta.data[,!grepl("SCT_snn_res.",colnames(citeCells_qc@meta.data))]
}
citeCells_qc <- RunPCA(citeCells_qc, verbose = FALSE)
citeCells_qc <- RunUMAP(citeCells_qc, dims = 1:30, verbose = FALSE)
citeCells_qc <- FindNeighbors(citeCells_qc, dims = 1:30, verbose = FALSE)
reso_used = c(0.1,0.4,0.8,1.2,1.8,2.4)
for (re in reso_used) {
  citeCells_qc <- FindClusters(citeCells_qc, verbose = FALSE,resolution=re)
}
DimPlot(citeCells_qc,group.by = "SCT_snn_res.1.2",cols = getPalette(nlevels(citeCells_qc$SCT_snn_res.1.2)), label=TRUE)+NoLegend()

ggsave(filename = "figs/umap_clustering_rna.pdf",width = 5,height = 5)
clustree(citeCells_qc, prefix = "SCT_snn_res.",show_axis = T)

pdf("figs/cluster_tree_rna.pdf")
clustree(citeCells_qc, prefix = "SCT_snn_res.",show_axis = T)
dev.off()
null device 
          1 

multi-level clustering on ADT data

citeCells_cit = citeCells_qc
citeCells_cit$RNA_clusters = citeCells_cit$seurat_clusters
DefaultAssay(citeCells_cit) <- "ADT"
citeCells_cit <- RunPCA(citeCells_cit, features = rownames(citeCells_cit),verbose = FALSE)
You're computing too large a percentage of total singular values, use a standard svd instead.
citeCells_cit <- RunUMAP(citeCells_cit,dim=1:20 , assay = "ADT")
citeCells_cit <- FindNeighbors(citeCells_cit)
Computing nearest neighbor graph
Computing SNN
if (any(grepl("ADT_snn_res.",colnames(citeCells_cit@meta.data)))){
  citeCells_cit@meta.data = citeCells_cit@meta.data[,!grepl("SCT_snn_res.",colnames(citeCells_cit@meta.data))]
}
citeCells_cit <- RunPCA(citeCells_cit, features = rownames(citeCells_cit), verbose = FALSE)
You're computing too large a percentage of total singular values, use a standard svd instead.
citeCells_cit <- RunUMAP(citeCells_cit, dims = 1:20, verbose = FALSE)
citeCells_cit <- FindNeighbors(citeCells_cit, dims = 1:20, verbose = FALSE)
reso_used = c(0.1,0.4,0.8,1.2,1.8,2.4)
for (re in reso_used) {
  citeCells_cit <- FindClusters(citeCells_cit, verbose = FALSE,resolution=re)
}
DimPlot(citeCells_cit,group.by = "ADT_snn_res.1.8",cols = getPalette1(nlevels(citeCells_cit$ADT_snn_res.1.8)), label=TRUE)+NoLegend()

ggsave(filename = "figs/umap_clustering_adt.pdf",width = 5,height = 5)
clustree(citeCells_cit, prefix = "ADT_snn_res.",show_axis = T)

pdf("figs/cluster_tree_adt.pdf")
clustree(citeCells_cit, prefix = "ADT_snn_res.",show_axis = T)
dev.off()
null device 
          1 
cal_entropy=function(x){
  freqs <- table(x)/length(x)
  freqs = freqs[freqs>0]
  return(-sum(freqs * log(freqs)))
}
get_cluster_entropy = function(c_assignment_A, c_assignment_B){
  ent = sapply(levels(c_assignment_A), function(x){cal_entropy(c_assignment_B[c_assignment_A==x])})
  ent_per_cell = rep(0,length(c_assignment_A))
  for (x in levels(c_assignment_A)){
    ent_per_cell[c_assignment_A==x]=ent[x]
  }
  return(ent_per_cell)
}

looking at RNA heterogeneity in ADT data

tmp_l = lapply(paste0("SCT_snn_res.",reso_used),function(x){get_cluster_entropy(citeCells_cit@meta.data[,paste0("ADT_snn_res.",reso_used[1])],citeCells_qc@meta.data[,x])})
ent_matrix = Reduce(cbind,tmp_l)
rownames(ent_matrix) = rownames(citeCells_cit@meta.data)
colnames(ent_matrix) = paste0("SCT_snn_res.",reso_used)
for (re in reso_used[-1]) {
  tmp_l = lapply(paste0("SCT_snn_res.",reso_used),function(x){get_cluster_entropy(citeCells_cit@meta.data[,paste0("ADT_snn_res.",re)],citeCells_qc@meta.data[,x])})
ent_matrix = ent_matrix+Reduce(cbind,tmp_l)
}
ent_matrix = ent_matrix/length(reso_used)
ent_matrix_rna = ent_matrix
ct = data.frame(UMAP_dim1=citeCells_cit@reductions$umap@cell.embeddings[,1],
                UMAP_dim2=citeCells_cit@reductions$umap@cell.embeddings[,2],
                Entropy=rowMeans(ent_matrix_rna),
                clusters=citeCells_cit@meta.data[,"ADT_snn_res.1.8"])
ct_grep = ct %>% group_by(clusters) %>% summarise(UMAP_dim1=mean(UMAP_dim1),UMAP_dim2=mean(UMAP_dim2))
ggplot()+
  geom_point(data=ct,aes(x=UMAP_dim1,y=UMAP_dim2,col=Entropy),size=0.5,alpha=0.8)+
  scale_color_gradientn(colours=BlueAndRed())+
  labs(col="Entropy",title="UMAP and cluster labels on ADT")+
  geom_text(data=ct_grep,aes(x=UMAP_dim1,y=UMAP_dim2,label=clusters))+
  theme_bw()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
panel.background = element_blank(), axis.line = element_line(colour = "black"))

ggsave(filename = "figs/UMAP_adt_entropy_on_rna.pdf")
Saving 7 x 7 in image

looking at ADT heterogeneity in RNA data

tmp_l = lapply(paste0("ADT_snn_res.",reso_used),function(x){get_cluster_entropy(citeCells_qc@meta.data[,paste0("SCT_snn_res.",reso_used[1])],citeCells_cit@meta.data[,x])})
ent_matrix = Reduce(cbind,tmp_l)
rownames(ent_matrix) = rownames(citeCells_cit@meta.data)
colnames(ent_matrix) = paste0("ADT_snn_res.",reso_used)
for (re in reso_used[-1]) {
  tmp_l = lapply(paste0("ADT_snn_res.",reso_used),function(x){get_cluster_entropy(citeCells_qc@meta.data[,paste0("SCT_snn_res.",re)],citeCells_cit@meta.data[,x])})
ent_matrix = ent_matrix+Reduce(cbind,tmp_l)
}
ent_matrix = ent_matrix/length(reso_used)
ent_matrix_adt = ent_matrix
ct = data.frame(UMAP_dim1=citeCells_qc@reductions$umap@cell.embeddings[,1],
                UMAP_dim2=citeCells_qc@reductions$umap@cell.embeddings[,2],
                Entropy=rowMeans(ent_matrix_adt),
                clusters=citeCells_qc@meta.data[,"SCT_snn_res.1.2"])
ct_grep = ct %>% group_by(clusters) %>% summarise(UMAP_dim1=mean(UMAP_dim1),UMAP_dim2=mean(UMAP_dim2))
ggplot()+
  geom_point(data=ct,aes(x=UMAP_dim1,y=UMAP_dim2,col=Entropy),size=0.5,alpha=0.8)+
  scale_color_gradientn(colours=BlueAndRed())+
  labs(col="Entropy",title="UMAP and cluster labels on RNA")+
  geom_text(data=ct_grep,aes(x=UMAP_dim1,y=UMAP_dim2,label=clusters))+
  theme_bw()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
panel.background = element_blank(), axis.line = element_line(colour = "black"))

use one-sense plot to visualize heterogeneity

citeCells_cit1 = RunUMAP(citeCells_cit,dims=1:20,n.components = 1)
citeCells_qc1 = RunUMAP(citeCells_qc,dims=1:30,n.components = 1)
ggplot(data = NULL,aes(x=citeCells_cit1@reductions$umap@cell.embeddings[,1],
                       y=citeCells_qc1@reductions$umap@cell.embeddings[,1],col=rowMeans(ent_matrix_adt)))+
  geom_point(size=0.5,alpha=0.6)+
  scale_color_gradientn(colours=BlueAndRed())+
  labs(x="UMAP on ADT",y="UMAP on RNA", col="Entropy")+
  theme_bw()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
panel.background = element_blank(), axis.line = element_line(colour = "black"))

ggplot(data = NULL,aes(x=citeCells_cit1@reductions$umap@cell.embeddings[,1],
                       y=citeCells_qc1@reductions$umap@cell.embeddings[,1],col=rowMeans(ent_matrix_rna)))+
  geom_point(size=0.5,alpha=0.6)+
  scale_color_gradientn(colours=BlueAndRed())+
  labs(x="UMAP on ADT",y="UMAP on RNA", col="Entropy")+
  theme_bw()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
panel.background = element_blank(), axis.line = element_line(colour = "black"))

look at gene expression

find marker ADTs for each cluster

Idents(citeCells_cit) = citeCells_cit$ADT_snn_res.1.8
citeCells_cit.markers <- FindAllMarkers(citeCells_cit, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25,verbose = FALSE)
top_ge <- citeCells_cit.markers %>% group_by(cluster) %>% top_n(n = 5, wt = avg_logFC)
tmp_mat = citeCells_cit@assays$ADT@scale.data[(top_ge$gene),]
tmp_mat[tmp_mat>2.5] = 2.5
tmp_mat[tmp_mat<(-2.5)] = -2.5
anno_df = data.frame(clusters=Idents(citeCells_cit),
                     log2_RNA_count=log2(citeCells_cit$nCount_RNA+1),
                     log2_ADT_count=log2(citeCells_cit$nCount_ADT+1),
                     number_of_genes=citeCells_cit$nFeature_RNA,stringsAsFactors = FALSE)
getPalette = colorRampPalette(brewer.pal(9, "Set1"))
col_clu = getPalette(nlevels(Idents(citeCells_cit)))
names(col_clu) = as.character(0:(nlevels(Idents(citeCells_cit))-1))
annotation_colors = list(
  clusters=col_clu,
  log2_RNA_count=BlueAndRed(),
  log2_ADT_count=BlueAndRed(),
  number_of_genes=BlueAndRed()
)
pheatmap(tmp_mat[,order(anno_df$clusters)],
         cluster_cols = FALSE, 
         cluster_rows = FALSE,
         annotation_col = anno_df,
         annotation_colors=annotation_colors,
         show_colnames = FALSE,
         color=PurpleAndYellow())

find marker genes for each cluster

Idents(citeCells_qc) = citeCells_qc$SCT_snn_res.1.2
citeCells_qc.markers <- FindAllMarkers(citeCells_qc, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25,verbose = FALSE)
top_ge <- citeCells_qc.markers %>% group_by(cluster) %>% top_n(n = 3, wt = avg_logFC)
tmp_mat = citeCells_qc@assays$SCT@scale.data[(top_ge$gene),]
tmp_mat[tmp_mat>2.5] = 2.5
tmp_mat[tmp_mat<(-2.5)] = -2.5
anno_df = data.frame(clusters=Idents(citeCells_qc),
                     log2_RNA_count=log2(citeCells_qc$nCount_RNA+1),
                     log2_ADT_count=log2(citeCells_qc$nCount_ADT+1),
                     number_of_genes=citeCells_qc$nFeature_RNA,stringsAsFactors = FALSE)
getPalette = colorRampPalette(brewer.pal(9, "Set1"))
col_clu = getPalette(nlevels(Idents(citeCells_qc)))
names(col_clu) = as.character(0:(nlevels(Idents(citeCells_qc))-1))
annotation_colors = list(
  clusters=col_clu,
  log2_RNA_count=BlueAndRed(),
  log2_ADT_count=BlueAndRed(),
  number_of_genes=BlueAndRed()
)
pheatmap(tmp_mat[,order(anno_df$clusters)],
         cluster_cols = FALSE, 
         cluster_rows = FALSE,
         annotation_col = anno_df,
         annotation_colors=annotation_colors,
         show_colnames = FALSE,
         color=PurpleAndYellow())

looking at cluster 3 in RNA (resolution=1.2), which has more heterogeneity in ADT data

citeCells_cit_sel = citeCells_cit[,Idents(citeCells_qc)=="3"]
citeCells_cit_sel <- RunPCA(citeCells_cit_sel, features = rownames(citeCells_cit_sel), verbose = FALSE)
You're computing too large a percentage of total singular values, use a standard svd instead.
citeCells_cit_sel <- FindNeighbors(citeCells_cit_sel, dims = 1:10, verbose = FALSE)
citeCells_cit_sel <- FindClusters(citeCells_cit_sel, verbose = FALSE,resolution=0.4)
citeCells_cit_sel.markers <- FindAllMarkers(citeCells_cit_sel, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25,verbose = FALSE)
top_ge <- citeCells_cit_sel.markers  %>% filter(p_val_adj<0.05) %>% group_by(cluster) %>% top_n(n = 5, wt = avg_logFC)
DoHeatmap(citeCells_cit_sel,features=top_ge$gene)+NoLegend()

citeCells_cit_sel <- SCTransform(citeCells_cit_sel, assay = "RNA",variable.features.n = 500, verbose = FALSE)
citeCells_cit_sel.rna.markers <- FindAllMarkers(citeCells_cit_sel, assay="SCT", only.pos = TRUE, min.pct = 0.15, logfc.threshold = 0.15,verbose = FALSE)
top_ge <- citeCells_cit_sel.rna.markers %>% filter(p_val_adj<0.05) %>% group_by(cluster) %>% top_n(n = 5, wt = avg_logFC)
DoHeatmap(citeCells_cit_sel,assay="SCT",features=top_ge$gene)

looking at cluster 7 in ADT (resolution=1.8), which has more heterogeneity in RNA data

citeCells_qc_sel = citeCells_qc[,Idents(citeCells_cit)=="7"]
citeCells_qc_sel <- SCTransform(citeCells_qc_sel, variable.features.n = 500, verbose = FALSE)
citeCells_qc_sel <- RunPCA(citeCells_qc_sel, verbose = FALSE)
citeCells_qc_sel <- FindNeighbors(citeCells_qc_sel, dims = 1:10, verbose = FALSE)
citeCells_qc_sel <- FindClusters(citeCells_qc_sel, verbose = FALSE,resolution=0.4)
citeCells_qc_sel.markers <- FindAllMarkers(citeCells_qc_sel, only.pos = TRUE, min.pct = 0.10, logfc.threshold = 0.15,verbose = FALSE)
RNA_clu = citeCells_qc_sel$seurat_clusters
top_ge <- citeCells_qc_sel.markers %>% group_by(cluster) %>% top_n(n =5, wt = avg_logFC)
DoHeatmap(citeCells_qc_sel,features=top_ge$gene)+NoLegend()

citeCells_qc_sel.adt.markers <- FindAllMarkers(citeCells_qc_sel, assay="ADT", only.pos = TRUE, min.pct = 0.15, logfc.threshold = 0.15,verbose = FALSE)
top_ge <- citeCells_qc_sel.adt.markers %>% filter(p_val_adj<0.05) %>% group_by(cluster) %>% top_n(n = 5, wt = avg_logFC)
DoHeatmap(citeCells_qc_sel,assay="ADT",features=c(top_ge$gene,"adt-CD69"))

top_ge = FindMarkers(citeCells_cit,ident.1="7",ident.2=c("0","1","2","3","4","8","10"),verbose = FALSE) 
top_ge$gene = rownames(top_ge)
top_ge$sign = "+"
top_ge$sign[top_ge$avg_logFC>0] = "-"
top_ge = top_ge %>% group_by(sign) %>% top_n(n = 7, wt = avg_logFC)
citeCells_cit_sel = citeCells_cit[,citeCells_cit$ADT_snn_res.1.8 %in% c("0","1","2","3","4","7","8","10")]
DoHeatmap(citeCells_cit_sel,features=top_ge$gene)+NoLegend()

pdf("figs/adt_markers_heatmap_cluster7.pdf")
DoHeatmap(citeCells_cit_sel,raster=FALSE,features=top_ge$gene)+NoLegend()
dev.off()
null device 
          1 

looking at cluster 11 in ADT (resolution=1.8), which has more heterogeneity in RNA data

citeCells_qc_sel = citeCells_qc[,citeCells_cit$ADT_snn_res.1.8=="11"]

top_ge <- citeCells_qc.markers %>% filter(cluster %in% unique(as.character( citeCells_qc_sel$SCT_snn_res.1.2))) %>% group_by(cluster) %>% top_n(n = 5, wt = avg_logFC)
DoHeatmap(citeCells_qc_sel,features=top_ge$gene)+NoLegend()
table(citeCells_cit$ADT_snn_res.1.8,citeCells_cit$ADT_snn_res.2.4)
top_ge = FindMarkers(citeCells_cit,ident.1="11",ident.2=c("5","6"),verbose = FALSE) 
top_ge$gene = rownames(top_ge)
top_ge$sign = "+"
top_ge$sign[top_ge$avg_logFC>0] = "-"
top_ge = top_ge %>% group_by(sign) %>% top_n(n = 10, wt = avg_logFC)
citeCells_cit_sel = citeCells_cit[,citeCells_cit$ADT_snn_res.1.8 %in% c("5","6","11")]
DoHeatmap(citeCells_cit_sel,features=top_ge$gene)+NoLegend()

pdf("figs/adt_markers_heatmap_cluster11.pdf")
DoHeatmap(citeCells_cit_sel,raster=FALSE,features=top_ge$gene)+NoLegend()
dev.off()
null device 
          1 
top_ge = FindMarkers(citeCells_cit, assay="SCT", ident.1="11",ident.2=c("5","6"), verbose = FALSE) 
top_ge$gene = rownames(top_ge)
top_ge$sign = "-"
top_ge$sign[top_ge$avg_logFC>0] = "+"
top_ge = top_ge %>% group_by(sign) %>% top_n(n = 5, wt = avg_logFC)
citeCells_cit_sel = citeCells_cit[,citeCells_cit$ADT_snn_res.1.8 %in% c("5","6","11")]
DoHeatmap(citeCells_cit_sel, assay = "SCT", features=top_ge$gene)+NoLegend()
The following features were omitted as they were not found in the scale.data slot for the SCT assay: N4BP2L2

LS0tCnRpdGxlOiAiY29tcGFyZSBjbHVzdGVyaW5nIHJlc3VsdHMgYmV0d2VlbiBSTkEgYW5kIEFEVCIKb3V0cHV0OiBodG1sX25vdGVib29rCmF1dGhvcjogTHV5aSBUaWFuCi0tLQoKCgpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGhlYXRtYXApCmxpYnJhcnkodW1hcCkKbGlicmFyeShkcGx5cikKbGlicmFyeShHR2FsbHkpCmxpYnJhcnkoZ2dFeHRyYSkKbGlicmFyeShjbHVzdHJlZSkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmdldFBhbGV0dGUgPSBjb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoOCwgIlNldDIiKSkKZ2V0UGFsZXR0ZTEgPSBjb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoOSwgIlNldDEiKSkKY2l0ZUNlbGxzX3FjID0gcmVhZFJEUygiZGF0YS9jaXRlQ2VsbHNfcWMuUmRzIikKYGBgCgojIG11bHRpLWxldmVsIGNsdXN0ZXJpbmcgb24gUk5BIGRhdGEKCmBgYHtyfQppZiAoYW55KGdyZXBsKCJTQ1Rfc25uX3Jlcy4iLGNvbG5hbWVzKGNpdGVDZWxsc19xY0BtZXRhLmRhdGEpKSkpewogIGNpdGVDZWxsc19xY0BtZXRhLmRhdGEgPSBjaXRlQ2VsbHNfcWNAbWV0YS5kYXRhWywhZ3JlcGwoIlNDVF9zbm5fcmVzLiIsY29sbmFtZXMoY2l0ZUNlbGxzX3FjQG1ldGEuZGF0YSkpXQp9CmNpdGVDZWxsc19xYyA8LSBSdW5QQ0EoY2l0ZUNlbGxzX3FjLCB2ZXJib3NlID0gRkFMU0UpCmNpdGVDZWxsc19xYyA8LSBSdW5VTUFQKGNpdGVDZWxsc19xYywgZGltcyA9IDE6MzAsIHZlcmJvc2UgPSBGQUxTRSkKY2l0ZUNlbGxzX3FjIDwtIEZpbmROZWlnaGJvcnMoY2l0ZUNlbGxzX3FjLCBkaW1zID0gMTozMCwgdmVyYm9zZSA9IEZBTFNFKQpyZXNvX3VzZWQgPSBjKDAuMSwwLjQsMC44LDEuMiwxLjgsMi40KQpmb3IgKHJlIGluIHJlc29fdXNlZCkgewogIGNpdGVDZWxsc19xYyA8LSBGaW5kQ2x1c3RlcnMoY2l0ZUNlbGxzX3FjLCB2ZXJib3NlID0gRkFMU0UscmVzb2x1dGlvbj1yZSkKfQpgYGAKCmBgYHtyfQpEaW1QbG90KGNpdGVDZWxsc19xYyxncm91cC5ieSA9ICJTQ1Rfc25uX3Jlcy4xLjIiLGNvbHMgPSBnZXRQYWxldHRlKG5sZXZlbHMoY2l0ZUNlbGxzX3FjJFNDVF9zbm5fcmVzLjEuMikpLCBsYWJlbD1UUlVFKStOb0xlZ2VuZCgpCmBgYAoKYGBge3J9Cmdnc2F2ZShmaWxlbmFtZSA9ICJmaWdzL3VtYXBfY2x1c3RlcmluZ19ybmEucGRmIix3aWR0aCA9IDUsaGVpZ2h0ID0gNSkKYGBgCgoKYGBge3IsZmlnLmhlaWdodD03LGZpZy53aWR0aD03fQpjbHVzdHJlZShjaXRlQ2VsbHNfcWMsIHByZWZpeCA9ICJTQ1Rfc25uX3Jlcy4iLHNob3dfYXhpcyA9IFQpCmBgYAoKYGBge3J9CnBkZigiZmlncy9jbHVzdGVyX3RyZWVfcm5hLnBkZiIpCmNsdXN0cmVlKGNpdGVDZWxsc19xYywgcHJlZml4ID0gIlNDVF9zbm5fcmVzLiIsc2hvd19heGlzID0gVCkKZGV2Lm9mZigpCmBgYAoKIyBtdWx0aS1sZXZlbCBjbHVzdGVyaW5nIG9uIEFEVCBkYXRhCgpgYGB7cn0KY2l0ZUNlbGxzX2NpdCA9IGNpdGVDZWxsc19xYwpjaXRlQ2VsbHNfY2l0JFJOQV9jbHVzdGVycyA9IGNpdGVDZWxsc19jaXQkc2V1cmF0X2NsdXN0ZXJzCkRlZmF1bHRBc3NheShjaXRlQ2VsbHNfY2l0KSA8LSAiQURUIgpjaXRlQ2VsbHNfY2l0IDwtIFJ1blBDQShjaXRlQ2VsbHNfY2l0LCBmZWF0dXJlcyA9IHJvd25hbWVzKGNpdGVDZWxsc19jaXQpLHZlcmJvc2UgPSBGQUxTRSkKY2l0ZUNlbGxzX2NpdCA8LSBSdW5VTUFQKGNpdGVDZWxsc19jaXQsZGltPTE6MjAgLCBhc3NheSA9ICJBRFQiKQpjaXRlQ2VsbHNfY2l0IDwtIEZpbmROZWlnaGJvcnMoY2l0ZUNlbGxzX2NpdCkKYGBgCgoKYGBge3J9CmlmIChhbnkoZ3JlcGwoIkFEVF9zbm5fcmVzLiIsY29sbmFtZXMoY2l0ZUNlbGxzX2NpdEBtZXRhLmRhdGEpKSkpewogIGNpdGVDZWxsc19jaXRAbWV0YS5kYXRhID0gY2l0ZUNlbGxzX2NpdEBtZXRhLmRhdGFbLCFncmVwbCgiU0NUX3Nubl9yZXMuIixjb2xuYW1lcyhjaXRlQ2VsbHNfY2l0QG1ldGEuZGF0YSkpXQp9CmNpdGVDZWxsc19jaXQgPC0gUnVuUENBKGNpdGVDZWxsc19jaXQsIGZlYXR1cmVzID0gcm93bmFtZXMoY2l0ZUNlbGxzX2NpdCksIHZlcmJvc2UgPSBGQUxTRSkKY2l0ZUNlbGxzX2NpdCA8LSBSdW5VTUFQKGNpdGVDZWxsc19jaXQsIGRpbXMgPSAxOjIwLCB2ZXJib3NlID0gRkFMU0UpCmNpdGVDZWxsc19jaXQgPC0gRmluZE5laWdoYm9ycyhjaXRlQ2VsbHNfY2l0LCBkaW1zID0gMToyMCwgdmVyYm9zZSA9IEZBTFNFKQpyZXNvX3VzZWQgPSBjKDAuMSwwLjQsMC44LDEuMiwxLjgsMi40KQpmb3IgKHJlIGluIHJlc29fdXNlZCkgewogIGNpdGVDZWxsc19jaXQgPC0gRmluZENsdXN0ZXJzKGNpdGVDZWxsc19jaXQsIHZlcmJvc2UgPSBGQUxTRSxyZXNvbHV0aW9uPXJlKQp9CmBgYAoKYGBge3J9CkRpbVBsb3QoY2l0ZUNlbGxzX2NpdCxncm91cC5ieSA9ICJBRFRfc25uX3Jlcy4xLjgiLGNvbHMgPSBnZXRQYWxldHRlMShubGV2ZWxzKGNpdGVDZWxsc19jaXQkQURUX3Nubl9yZXMuMS44KSksIGxhYmVsPVRSVUUpK05vTGVnZW5kKCkKYGBgCgpgYGB7cn0KZ2dzYXZlKGZpbGVuYW1lID0gImZpZ3MvdW1hcF9jbHVzdGVyaW5nX2FkdC5wZGYiLHdpZHRoID0gNSxoZWlnaHQgPSA1KQpgYGAKCmBgYHtyLGZpZy5oZWlnaHQ9NyxmaWcud2lkdGg9N30KY2x1c3RyZWUoY2l0ZUNlbGxzX2NpdCwgcHJlZml4ID0gIkFEVF9zbm5fcmVzLiIsc2hvd19heGlzID0gVCkKYGBgCgpgYGB7cn0KcGRmKCJmaWdzL2NsdXN0ZXJfdHJlZV9hZHQucGRmIikKY2x1c3RyZWUoY2l0ZUNlbGxzX2NpdCwgcHJlZml4ID0gIkFEVF9zbm5fcmVzLiIsc2hvd19heGlzID0gVCkKZGV2Lm9mZigpCmBgYAoKCmBgYHtyfQpjYWxfZW50cm9weT1mdW5jdGlvbih4KXsKICBmcmVxcyA8LSB0YWJsZSh4KS9sZW5ndGgoeCkKICBmcmVxcyA9IGZyZXFzW2ZyZXFzPjBdCiAgcmV0dXJuKC1zdW0oZnJlcXMgKiBsb2coZnJlcXMpKSkKfQoKZ2V0X2NsdXN0ZXJfZW50cm9weSA9IGZ1bmN0aW9uKGNfYXNzaWdubWVudF9BLCBjX2Fzc2lnbm1lbnRfQil7CiAgZW50ID0gc2FwcGx5KGxldmVscyhjX2Fzc2lnbm1lbnRfQSksIGZ1bmN0aW9uKHgpe2NhbF9lbnRyb3B5KGNfYXNzaWdubWVudF9CW2NfYXNzaWdubWVudF9BPT14XSl9KQogIGVudF9wZXJfY2VsbCA9IHJlcCgwLGxlbmd0aChjX2Fzc2lnbm1lbnRfQSkpCiAgZm9yICh4IGluIGxldmVscyhjX2Fzc2lnbm1lbnRfQSkpewogICAgZW50X3Blcl9jZWxsW2NfYXNzaWdubWVudF9BPT14XT1lbnRbeF0KICB9CiAgcmV0dXJuKGVudF9wZXJfY2VsbCkKfQpgYGAKCiMjIGxvb2tpbmcgYXQgUk5BIGhldGVyb2dlbmVpdHkgaW4gQURUIGRhdGEKCmBgYHtyfQp0bXBfbCA9IGxhcHBseShwYXN0ZTAoIlNDVF9zbm5fcmVzLiIscmVzb191c2VkKSxmdW5jdGlvbih4KXtnZXRfY2x1c3Rlcl9lbnRyb3B5KGNpdGVDZWxsc19jaXRAbWV0YS5kYXRhWyxwYXN0ZTAoIkFEVF9zbm5fcmVzLiIscmVzb191c2VkWzFdKV0sY2l0ZUNlbGxzX3FjQG1ldGEuZGF0YVsseF0pfSkKZW50X21hdHJpeCA9IFJlZHVjZShjYmluZCx0bXBfbCkKcm93bmFtZXMoZW50X21hdHJpeCkgPSByb3duYW1lcyhjaXRlQ2VsbHNfY2l0QG1ldGEuZGF0YSkKY29sbmFtZXMoZW50X21hdHJpeCkgPSBwYXN0ZTAoIlNDVF9zbm5fcmVzLiIscmVzb191c2VkKQoKZm9yIChyZSBpbiByZXNvX3VzZWRbLTFdKSB7CiAgdG1wX2wgPSBsYXBwbHkocGFzdGUwKCJTQ1Rfc25uX3Jlcy4iLHJlc29fdXNlZCksZnVuY3Rpb24oeCl7Z2V0X2NsdXN0ZXJfZW50cm9weShjaXRlQ2VsbHNfY2l0QG1ldGEuZGF0YVsscGFzdGUwKCJBRFRfc25uX3Jlcy4iLHJlKV0sY2l0ZUNlbGxzX3FjQG1ldGEuZGF0YVsseF0pfSkKZW50X21hdHJpeCA9IGVudF9tYXRyaXgrUmVkdWNlKGNiaW5kLHRtcF9sKQp9CmVudF9tYXRyaXggPSBlbnRfbWF0cml4L2xlbmd0aChyZXNvX3VzZWQpCmVudF9tYXRyaXhfcm5hID0gZW50X21hdHJpeApgYGAKCgpgYGB7cn0KY3QgPSBkYXRhLmZyYW1lKFVNQVBfZGltMT1jaXRlQ2VsbHNfY2l0QHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3NbLDFdLAogICAgICAgICAgICAgICAgVU1BUF9kaW0yPWNpdGVDZWxsc19jaXRAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1ssMl0sCiAgICAgICAgICAgICAgICBFbnRyb3B5PXJvd01lYW5zKGVudF9tYXRyaXhfcm5hKSwKICAgICAgICAgICAgICAgIGNsdXN0ZXJzPWNpdGVDZWxsc19jaXRAbWV0YS5kYXRhWywiQURUX3Nubl9yZXMuMS44Il0pCmN0X2dyZXAgPSBjdCAlPiUgZ3JvdXBfYnkoY2x1c3RlcnMpICU+JSBzdW1tYXJpc2UoVU1BUF9kaW0xPW1lYW4oVU1BUF9kaW0xKSxVTUFQX2RpbTI9bWVhbihVTUFQX2RpbTIpKQpnZ3Bsb3QoKSsKICBnZW9tX3BvaW50KGRhdGE9Y3QsYWVzKHg9VU1BUF9kaW0xLHk9VU1BUF9kaW0yLGNvbD1FbnRyb3B5KSxzaXplPTAuNSxhbHBoYT0wLjgpKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzPUJsdWVBbmRSZWQoKSkrCiAgbGFicyhjb2w9IkVudHJvcHkiLHRpdGxlPSJVTUFQIGFuZCBjbHVzdGVyIGxhYmVscyBvbiBBRFQiKSsKICBnZW9tX3RleHQoZGF0YT1jdF9ncmVwLGFlcyh4PVVNQVBfZGltMSx5PVVNQVBfZGltMixsYWJlbD1jbHVzdGVycykpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIpKQpgYGAKCmBgYHtyfQpnZ3NhdmUoZmlsZW5hbWUgPSAiZmlncy9VTUFQX2FkdF9lbnRyb3B5X29uX3JuYS5wZGYiLHdpZHRoID0gNS41LGhlaWdodCA9IDUpCmBgYAoKCiMjIGxvb2tpbmcgYXQgQURUIGhldGVyb2dlbmVpdHkgaW4gUk5BIGRhdGEKCmBgYHtyfQp0bXBfbCA9IGxhcHBseShwYXN0ZTAoIkFEVF9zbm5fcmVzLiIscmVzb191c2VkKSxmdW5jdGlvbih4KXtnZXRfY2x1c3Rlcl9lbnRyb3B5KGNpdGVDZWxsc19xY0BtZXRhLmRhdGFbLHBhc3RlMCgiU0NUX3Nubl9yZXMuIixyZXNvX3VzZWRbMV0pXSxjaXRlQ2VsbHNfY2l0QG1ldGEuZGF0YVsseF0pfSkKZW50X21hdHJpeCA9IFJlZHVjZShjYmluZCx0bXBfbCkKcm93bmFtZXMoZW50X21hdHJpeCkgPSByb3duYW1lcyhjaXRlQ2VsbHNfY2l0QG1ldGEuZGF0YSkKY29sbmFtZXMoZW50X21hdHJpeCkgPSBwYXN0ZTAoIkFEVF9zbm5fcmVzLiIscmVzb191c2VkKQoKZm9yIChyZSBpbiByZXNvX3VzZWRbLTFdKSB7CiAgdG1wX2wgPSBsYXBwbHkocGFzdGUwKCJBRFRfc25uX3Jlcy4iLHJlc29fdXNlZCksZnVuY3Rpb24oeCl7Z2V0X2NsdXN0ZXJfZW50cm9weShjaXRlQ2VsbHNfcWNAbWV0YS5kYXRhWyxwYXN0ZTAoIlNDVF9zbm5fcmVzLiIscmUpXSxjaXRlQ2VsbHNfY2l0QG1ldGEuZGF0YVsseF0pfSkKZW50X21hdHJpeCA9IGVudF9tYXRyaXgrUmVkdWNlKGNiaW5kLHRtcF9sKQp9CmVudF9tYXRyaXggPSBlbnRfbWF0cml4L2xlbmd0aChyZXNvX3VzZWQpCmVudF9tYXRyaXhfYWR0ID0gZW50X21hdHJpeApgYGAKCgpgYGB7cn0KY3QgPSBkYXRhLmZyYW1lKFVNQVBfZGltMT1jaXRlQ2VsbHNfcWNAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1ssMV0sCiAgICAgICAgICAgICAgICBVTUFQX2RpbTI9Y2l0ZUNlbGxzX3FjQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3NbLDJdLAogICAgICAgICAgICAgICAgRW50cm9weT1yb3dNZWFucyhlbnRfbWF0cml4X2FkdCksCiAgICAgICAgICAgICAgICBjbHVzdGVycz1jaXRlQ2VsbHNfcWNAbWV0YS5kYXRhWywiU0NUX3Nubl9yZXMuMS4yIl0pCmN0X2dyZXAgPSBjdCAlPiUgZ3JvdXBfYnkoY2x1c3RlcnMpICU+JSBzdW1tYXJpc2UoVU1BUF9kaW0xPW1lYW4oVU1BUF9kaW0xKSxVTUFQX2RpbTI9bWVhbihVTUFQX2RpbTIpKQpnZ3Bsb3QoKSsKICBnZW9tX3BvaW50KGRhdGE9Y3QsYWVzKHg9VU1BUF9kaW0xLHk9VU1BUF9kaW0yLGNvbD1FbnRyb3B5KSxzaXplPTAuNSxhbHBoYT0wLjgpKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzPUJsdWVBbmRSZWQoKSkrCiAgbGFicyhjb2w9IkVudHJvcHkiLHRpdGxlPSJVTUFQIGFuZCBjbHVzdGVyIGxhYmVscyBvbiBSTkEiKSsKICBnZW9tX3RleHQoZGF0YT1jdF9ncmVwLGFlcyh4PVVNQVBfZGltMSx5PVVNQVBfZGltMixsYWJlbD1jbHVzdGVycykpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIpKQpgYGAKCiMjIHVzZSBvbmUtc2Vuc2UgcGxvdCB0byB2aXN1YWxpemUgaGV0ZXJvZ2VuZWl0eQoKYGBge3J9CmNpdGVDZWxsc19jaXQxID0gUnVuVU1BUChjaXRlQ2VsbHNfY2l0LGRpbXM9MToyMCxuLmNvbXBvbmVudHMgPSAxKQpjaXRlQ2VsbHNfcWMxID0gUnVuVU1BUChjaXRlQ2VsbHNfcWMsZGltcz0xOjMwLG4uY29tcG9uZW50cyA9IDEpCmBgYAoKYGBge3J9CmdncGxvdChkYXRhID0gTlVMTCxhZXMoeD1jaXRlQ2VsbHNfY2l0MUByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzWywxXSwKICAgICAgICAgICAgICAgICAgICAgICB5PWNpdGVDZWxsc19xYzFAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1ssMV0sY29sPXJvd01lYW5zKGVudF9tYXRyaXhfYWR0KSkpKwogIGdlb21fcG9pbnQoc2l6ZT0wLjUsYWxwaGE9MC42KSsKICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3Vycz1CbHVlQW5kUmVkKCkpKwogIGxhYnMoeD0iVU1BUCBvbiBBRFQiLHk9IlVNQVAgb24gUk5BIiwgY29sPSJFbnRyb3B5IikrCiAgdGhlbWVfYncoKSsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLApwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIikpCmBgYAoKYGBge3J9CmdncGxvdChkYXRhID0gTlVMTCxhZXMoeD1jaXRlQ2VsbHNfY2l0MUByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzWywxXSwKICAgICAgICAgICAgICAgICAgICAgICB5PWNpdGVDZWxsc19xYzFAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1ssMV0sY29sPXJvd01lYW5zKGVudF9tYXRyaXhfcm5hKSkpKwogIGdlb21fcG9pbnQoc2l6ZT0wLjUsYWxwaGE9MC42KSsKICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3Vycz1CbHVlQW5kUmVkKCkpKwogIGxhYnMoeD0iVU1BUCBvbiBBRFQiLHk9IlVNQVAgb24gUk5BIiwgY29sPSJFbnRyb3B5IikrCiAgdGhlbWVfYncoKSsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLApwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIikpCmBgYAoKYGBge3J9Cmdnc2F2ZSgiZmlncy8xZC11bWFwX2FkdF9ybmFfZW50cm9weV9vbl9ybmEucGRmIix3aWR0aCA9IDUuNSxoZWlnaHQgPSA1KQpgYGAKCgojIGxvb2sgYXQgZ2VuZSBleHByZXNzaW9uIAoKZmluZCBtYXJrZXIgQURUcyBmb3IgZWFjaCBjbHVzdGVyCmBgYHtyfQpJZGVudHMoY2l0ZUNlbGxzX2NpdCkgPSBjaXRlQ2VsbHNfY2l0JEFEVF9zbm5fcmVzLjEuOApjaXRlQ2VsbHNfY2l0Lm1hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoY2l0ZUNlbGxzX2NpdCwgb25seS5wb3MgPSBUUlVFLCBtaW4ucGN0ID0gMC4yNSwgbG9nZmMudGhyZXNob2xkID0gMC4yNSx2ZXJib3NlID0gRkFMU0UpCmBgYAoKYGBge3IsZmlnLmhlaWdodD04LGZpZy53aWR0aD05fQp0b3BfZ2UgPC0gY2l0ZUNlbGxzX2NpdC5tYXJrZXJzICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgdG9wX24obiA9IDUsIHd0ID0gYXZnX2xvZ0ZDKQp0bXBfbWF0ID0gY2l0ZUNlbGxzX2NpdEBhc3NheXMkQURUQHNjYWxlLmRhdGFbKHRvcF9nZSRnZW5lKSxdCnRtcF9tYXRbdG1wX21hdD4yLjVdID0gMi41CnRtcF9tYXRbdG1wX21hdDwoLTIuNSldID0gLTIuNQoKYW5ub19kZiA9IGRhdGEuZnJhbWUoY2x1c3RlcnM9SWRlbnRzKGNpdGVDZWxsc19jaXQpLAogICAgICAgICAgICAgICAgICAgICBsb2cyX1JOQV9jb3VudD1sb2cyKGNpdGVDZWxsc19jaXQkbkNvdW50X1JOQSsxKSwKICAgICAgICAgICAgICAgICAgICAgbG9nMl9BRFRfY291bnQ9bG9nMihjaXRlQ2VsbHNfY2l0JG5Db3VudF9BRFQrMSksCiAgICAgICAgICAgICAgICAgICAgIG51bWJlcl9vZl9nZW5lcz1jaXRlQ2VsbHNfY2l0JG5GZWF0dXJlX1JOQSxzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmdldFBhbGV0dGUgPSBjb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoOSwgIlNldDEiKSkKY29sX2NsdSA9IGdldFBhbGV0dGUobmxldmVscyhJZGVudHMoY2l0ZUNlbGxzX2NpdCkpKQpuYW1lcyhjb2xfY2x1KSA9IGFzLmNoYXJhY3RlcigwOihubGV2ZWxzKElkZW50cyhjaXRlQ2VsbHNfY2l0KSktMSkpCmFubm90YXRpb25fY29sb3JzID0gbGlzdCgKICBjbHVzdGVycz1jb2xfY2x1LAogIGxvZzJfUk5BX2NvdW50PUJsdWVBbmRSZWQoKSwKICBsb2cyX0FEVF9jb3VudD1CbHVlQW5kUmVkKCksCiAgbnVtYmVyX29mX2dlbmVzPUJsdWVBbmRSZWQoKQopCgpwaGVhdG1hcCh0bXBfbWF0WyxvcmRlcihhbm5vX2RmJGNsdXN0ZXJzKV0sCiAgICAgICAgIGNsdXN0ZXJfY29scyA9IEZBTFNFLCAKICAgICAgICAgY2x1c3Rlcl9yb3dzID0gRkFMU0UsCiAgICAgICAgIGFubm90YXRpb25fY29sID0gYW5ub19kZiwKICAgICAgICAgYW5ub3RhdGlvbl9jb2xvcnM9YW5ub3RhdGlvbl9jb2xvcnMsCiAgICAgICAgIHNob3dfY29sbmFtZXMgPSBGQUxTRSwKICAgICAgICAgY29sb3I9UHVycGxlQW5kWWVsbG93KCkpCmBgYAoKCmZpbmQgbWFya2VyIGdlbmVzIGZvciBlYWNoIGNsdXN0ZXIKYGBge3J9CklkZW50cyhjaXRlQ2VsbHNfcWMpID0gY2l0ZUNlbGxzX3FjJFNDVF9zbm5fcmVzLjEuMgpjaXRlQ2VsbHNfcWMubWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhjaXRlQ2VsbHNfcWMsIG9ubHkucG9zID0gVFJVRSwgbWluLnBjdCA9IDAuMjUsIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMjUsdmVyYm9zZSA9IEZBTFNFKQpgYGAKCmBgYHtyLGZpZy5oZWlnaHQ9OCxmaWcud2lkdGg9OH0KdG9wX2dlIDwtIGNpdGVDZWxsc19xYy5tYXJrZXJzICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgdG9wX24obiA9IDMsIHd0ID0gYXZnX2xvZ0ZDKQp0bXBfbWF0ID0gY2l0ZUNlbGxzX3FjQGFzc2F5cyRTQ1RAc2NhbGUuZGF0YVsodG9wX2dlJGdlbmUpLF0KdG1wX21hdFt0bXBfbWF0PjIuNV0gPSAyLjUKdG1wX21hdFt0bXBfbWF0PCgtMi41KV0gPSAtMi41Cgphbm5vX2RmID0gZGF0YS5mcmFtZShjbHVzdGVycz1JZGVudHMoY2l0ZUNlbGxzX3FjKSwKICAgICAgICAgICAgICAgICAgICAgbG9nMl9STkFfY291bnQ9bG9nMihjaXRlQ2VsbHNfcWMkbkNvdW50X1JOQSsxKSwKICAgICAgICAgICAgICAgICAgICAgbG9nMl9BRFRfY291bnQ9bG9nMihjaXRlQ2VsbHNfcWMkbkNvdW50X0FEVCsxKSwKICAgICAgICAgICAgICAgICAgICAgbnVtYmVyX29mX2dlbmVzPWNpdGVDZWxsc19xYyRuRmVhdHVyZV9STkEsc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpnZXRQYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDksICJTZXQxIikpCmNvbF9jbHUgPSBnZXRQYWxldHRlKG5sZXZlbHMoSWRlbnRzKGNpdGVDZWxsc19xYykpKQpuYW1lcyhjb2xfY2x1KSA9IGFzLmNoYXJhY3RlcigwOihubGV2ZWxzKElkZW50cyhjaXRlQ2VsbHNfcWMpKS0xKSkKYW5ub3RhdGlvbl9jb2xvcnMgPSBsaXN0KAogIGNsdXN0ZXJzPWNvbF9jbHUsCiAgbG9nMl9STkFfY291bnQ9Qmx1ZUFuZFJlZCgpLAogIGxvZzJfQURUX2NvdW50PUJsdWVBbmRSZWQoKSwKICBudW1iZXJfb2ZfZ2VuZXM9Qmx1ZUFuZFJlZCgpCikKCnBoZWF0bWFwKHRtcF9tYXRbLG9yZGVyKGFubm9fZGYkY2x1c3RlcnMpXSwKICAgICAgICAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsIAogICAgICAgICBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwKICAgICAgICAgYW5ub3RhdGlvbl9jb2wgPSBhbm5vX2RmLAogICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycz1hbm5vdGF0aW9uX2NvbG9ycywKICAgICAgICAgc2hvd19jb2xuYW1lcyA9IEZBTFNFLAogICAgICAgICBjb2xvcj1QdXJwbGVBbmRZZWxsb3coKSkKYGBgCgpsb29raW5nIGF0IGNsdXN0ZXIgYDNgIGluIFJOQSAocmVzb2x1dGlvbj0xLjIpLCB3aGljaCBoYXMgbW9yZSBoZXRlcm9nZW5laXR5IGluIEFEVCBkYXRhCgpgYGB7cixmaWcuaGVpZ2h0PTQsZmlnLndpZHRoPTZ9CmNpdGVDZWxsc19jaXRfc2VsID0gY2l0ZUNlbGxzX2NpdFssSWRlbnRzKGNpdGVDZWxsc19xYyk9PSIzIl0KY2l0ZUNlbGxzX2NpdF9zZWwgPC0gUnVuUENBKGNpdGVDZWxsc19jaXRfc2VsLCBmZWF0dXJlcyA9IHJvd25hbWVzKGNpdGVDZWxsc19jaXRfc2VsKSwgdmVyYm9zZSA9IEZBTFNFKQpjaXRlQ2VsbHNfY2l0X3NlbCA8LSBGaW5kTmVpZ2hib3JzKGNpdGVDZWxsc19jaXRfc2VsLCBkaW1zID0gMToxMCwgdmVyYm9zZSA9IEZBTFNFKQpjaXRlQ2VsbHNfY2l0X3NlbCA8LSBGaW5kQ2x1c3RlcnMoY2l0ZUNlbGxzX2NpdF9zZWwsIHZlcmJvc2UgPSBGQUxTRSxyZXNvbHV0aW9uPTAuNCkKY2l0ZUNlbGxzX2NpdF9zZWwubWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhjaXRlQ2VsbHNfY2l0X3NlbCwgb25seS5wb3MgPSBUUlVFLCBtaW4ucGN0ID0gMC4yNSwgbG9nZmMudGhyZXNob2xkID0gMC4yNSx2ZXJib3NlID0gRkFMU0UpCgp0b3BfZ2UgPC0gY2l0ZUNlbGxzX2NpdF9zZWwubWFya2VycyAgJT4lIGZpbHRlcihwX3ZhbF9hZGo8MC4wNSkgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSB0b3BfbihuID0gNSwgd3QgPSBhdmdfbG9nRkMpCkRvSGVhdG1hcChjaXRlQ2VsbHNfY2l0X3NlbCxmZWF0dXJlcz10b3BfZ2UkZ2VuZSkrTm9MZWdlbmQoKQpgYGAKCgpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9CmNpdGVDZWxsc19jaXRfc2VsIDwtIFNDVHJhbnNmb3JtKGNpdGVDZWxsc19jaXRfc2VsLCBhc3NheSA9ICJSTkEiLHZhcmlhYmxlLmZlYXR1cmVzLm4gPSA1MDAsIHZlcmJvc2UgPSBGQUxTRSkKY2l0ZUNlbGxzX2NpdF9zZWwucm5hLm1hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoY2l0ZUNlbGxzX2NpdF9zZWwsIGFzc2F5PSJTQ1QiLCBvbmx5LnBvcyA9IFRSVUUsIG1pbi5wY3QgPSAwLjE1LCBsb2dmYy50aHJlc2hvbGQgPSAwLjE1LHZlcmJvc2UgPSBGQUxTRSkKdG9wX2dlIDwtIGNpdGVDZWxsc19jaXRfc2VsLnJuYS5tYXJrZXJzICU+JSBmaWx0ZXIocF92YWxfYWRqPDAuMDUpICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgdG9wX24obiA9IDUsIHd0ID0gYXZnX2xvZ0ZDKQpEb0hlYXRtYXAoY2l0ZUNlbGxzX2NpdF9zZWwsYXNzYXk9IlNDVCIsZmVhdHVyZXM9dG9wX2dlJGdlbmUpCmBgYAoKbG9va2luZyBhdCBjbHVzdGVyIGA3YCBpbiBBRFQgKHJlc29sdXRpb249MS44KSwgd2hpY2ggaGFzIG1vcmUgaGV0ZXJvZ2VuZWl0eSBpbiBSTkEgZGF0YQoKYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFLGZpZy5oZWlnaHQ9NSxmaWcud2lkdGg9N30KY2l0ZUNlbGxzX3FjX3NlbCA9IGNpdGVDZWxsc19xY1ssSWRlbnRzKGNpdGVDZWxsc19jaXQpPT0iNyJdCmNpdGVDZWxsc19xY19zZWwgPC0gU0NUcmFuc2Zvcm0oY2l0ZUNlbGxzX3FjX3NlbCwgdmFyaWFibGUuZmVhdHVyZXMubiA9IDUwMCwgdmVyYm9zZSA9IEZBTFNFKQpjaXRlQ2VsbHNfcWNfc2VsIDwtIFJ1blBDQShjaXRlQ2VsbHNfcWNfc2VsLCB2ZXJib3NlID0gRkFMU0UpCmNpdGVDZWxsc19xY19zZWwgPC0gRmluZE5laWdoYm9ycyhjaXRlQ2VsbHNfcWNfc2VsLCBkaW1zID0gMToxMCwgdmVyYm9zZSA9IEZBTFNFKQpjaXRlQ2VsbHNfcWNfc2VsIDwtIEZpbmRDbHVzdGVycyhjaXRlQ2VsbHNfcWNfc2VsLCB2ZXJib3NlID0gRkFMU0UscmVzb2x1dGlvbj0wLjQpCmNpdGVDZWxsc19xY19zZWwubWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhjaXRlQ2VsbHNfcWNfc2VsLCBvbmx5LnBvcyA9IFRSVUUsIG1pbi5wY3QgPSAwLjEwLCBsb2dmYy50aHJlc2hvbGQgPSAwLjE1LHZlcmJvc2UgPSBGQUxTRSkKUk5BX2NsdSA9IGNpdGVDZWxsc19xY19zZWwkc2V1cmF0X2NsdXN0ZXJzCnRvcF9nZSA8LSBjaXRlQ2VsbHNfcWNfc2VsLm1hcmtlcnMgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSB0b3BfbihuID01LCB3dCA9IGF2Z19sb2dGQykKRG9IZWF0bWFwKGNpdGVDZWxsc19xY19zZWwsZmVhdHVyZXM9dG9wX2dlJGdlbmUpK05vTGVnZW5kKCkKYGBgCgoKYGBge3J9CmNpdGVDZWxsc19xY19zZWwuYWR0Lm1hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoY2l0ZUNlbGxzX3FjX3NlbCwgYXNzYXk9IkFEVCIsIG9ubHkucG9zID0gVFJVRSwgbWluLnBjdCA9IDAuMTUsIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMTUsdmVyYm9zZSA9IEZBTFNFKQp0b3BfZ2UgPC0gY2l0ZUNlbGxzX3FjX3NlbC5hZHQubWFya2VycyAlPiUgZmlsdGVyKHBfdmFsX2FkajwwLjA1KSAlPiUgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIHRvcF9uKG4gPSA1LCB3dCA9IGF2Z19sb2dGQykKCkRvSGVhdG1hcChjaXRlQ2VsbHNfcWNfc2VsLGFzc2F5PSJBRFQiLGZlYXR1cmVzPWModG9wX2dlJGdlbmUsImFkdC1DRDY5IikpCmBgYAoKCmBgYHtyfQp0b3BfZ2UgPSBGaW5kTWFya2VycyhjaXRlQ2VsbHNfY2l0LGlkZW50LjE9IjciLGlkZW50LjI9YygiMCIsIjEiLCIyIiwiMyIsIjQiLCI4IiwiMTAiKSx2ZXJib3NlID0gRkFMU0UpIAp0b3BfZ2UkZ2VuZSA9IHJvd25hbWVzKHRvcF9nZSkKdG9wX2dlJHNpZ24gPSAiKyIKdG9wX2dlJHNpZ25bdG9wX2dlJGF2Z19sb2dGQz4wXSA9ICItIgp0b3BfZ2UgPSB0b3BfZ2UgJT4lIGdyb3VwX2J5KHNpZ24pICU+JSB0b3BfbihuID0gNywgd3QgPSBhdmdfbG9nRkMpCmNpdGVDZWxsc19jaXRfc2VsID0gY2l0ZUNlbGxzX2NpdFssY2l0ZUNlbGxzX2NpdCRBRFRfc25uX3Jlcy4xLjggJWluJSBjKCIwIiwiMSIsIjIiLCIzIiwiNCIsIjciLCI4IiwiMTAiKV0KRG9IZWF0bWFwKGNpdGVDZWxsc19jaXRfc2VsLGZlYXR1cmVzPXRvcF9nZSRnZW5lKStOb0xlZ2VuZCgpCmBgYAoKYGBge3J9CnBkZigiZmlncy9hZHRfbWFya2Vyc19oZWF0bWFwX2NsdXN0ZXI3LnBkZiIpCkRvSGVhdG1hcChjaXRlQ2VsbHNfY2l0X3NlbCxyYXN0ZXI9RkFMU0UsZmVhdHVyZXM9dG9wX2dlJGdlbmUpK05vTGVnZW5kKCkKZGV2Lm9mZigpCmBgYAoKCmxvb2tpbmcgYXQgY2x1c3RlciBgMTFgIGluIEFEVCAocmVzb2x1dGlvbj0xLjgpLCB3aGljaCBoYXMgbW9yZSBoZXRlcm9nZW5laXR5IGluIFJOQSBkYXRhCgpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0UsZmlnLmhlaWdodD01LGZpZy53aWR0aD03fQpjaXRlQ2VsbHNfcWNfc2VsID0gY2l0ZUNlbGxzX3FjWyxjaXRlQ2VsbHNfY2l0JEFEVF9zbm5fcmVzLjEuOD09IjExIl0KCnRvcF9nZSA8LSBjaXRlQ2VsbHNfcWMubWFya2VycyAlPiUgZmlsdGVyKGNsdXN0ZXIgJWluJSB1bmlxdWUoYXMuY2hhcmFjdGVyKCBjaXRlQ2VsbHNfcWNfc2VsJFNDVF9zbm5fcmVzLjEuMikpKSAlPiUgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIHRvcF9uKG4gPSA1LCB3dCA9IGF2Z19sb2dGQykKRG9IZWF0bWFwKGNpdGVDZWxsc19xY19zZWwsZmVhdHVyZXM9dG9wX2dlJGdlbmUpK05vTGVnZW5kKCkKYGBgCgpgYGB7cn0KdGFibGUoY2l0ZUNlbGxzX2NpdCRBRFRfc25uX3Jlcy4xLjgsY2l0ZUNlbGxzX2NpdCRBRFRfc25uX3Jlcy4yLjQpCmBgYAoKYGBge3J9CnRvcF9nZSA9IEZpbmRNYXJrZXJzKGNpdGVDZWxsc19jaXQsaWRlbnQuMT0iMTEiLGlkZW50LjI9YygiNSIsIjYiKSx2ZXJib3NlID0gRkFMU0UpIAp0b3BfZ2UkZ2VuZSA9IHJvd25hbWVzKHRvcF9nZSkKdG9wX2dlJHNpZ24gPSAiKyIKdG9wX2dlJHNpZ25bdG9wX2dlJGF2Z19sb2dGQz4wXSA9ICItIgp0b3BfZ2UgPSB0b3BfZ2UgJT4lIGdyb3VwX2J5KHNpZ24pICU+JSB0b3BfbihuID0gMTAsIHd0ID0gYXZnX2xvZ0ZDKQpjaXRlQ2VsbHNfY2l0X3NlbCA9IGNpdGVDZWxsc19jaXRbLGNpdGVDZWxsc19jaXQkQURUX3Nubl9yZXMuMS44ICVpbiUgYygiNSIsIjYiLCIxMSIpXQpEb0hlYXRtYXAoY2l0ZUNlbGxzX2NpdF9zZWwsZmVhdHVyZXM9dG9wX2dlJGdlbmUpK05vTGVnZW5kKCkKYGBgCgpgYGB7cn0KcGRmKCJmaWdzL2FkdF9tYXJrZXJzX2hlYXRtYXBfY2x1c3RlcjExLnBkZiIpCkRvSGVhdG1hcChjaXRlQ2VsbHNfY2l0X3NlbCxyYXN0ZXI9RkFMU0UsZmVhdHVyZXM9dG9wX2dlJGdlbmUpK05vTGVnZW5kKCkKZGV2Lm9mZigpCmBgYAoKCmBgYHtyfQp0b3BfZ2UgPSBGaW5kTWFya2VycyhjaXRlQ2VsbHNfY2l0LCBhc3NheT0iU0NUIiwgaWRlbnQuMT0iMTEiLGlkZW50LjI9YygiNSIsIjYiKSwgdmVyYm9zZSA9IEZBTFNFKSAKdG9wX2dlJGdlbmUgPSByb3duYW1lcyh0b3BfZ2UpCnRvcF9nZSRzaWduID0gIi0iCnRvcF9nZSRzaWduW3RvcF9nZSRhdmdfbG9nRkM+MF0gPSAiKyIKdG9wX2dlID0gdG9wX2dlICU+JSBncm91cF9ieShzaWduKSAlPiUgdG9wX24obiA9IDUsIHd0ID0gYXZnX2xvZ0ZDKQpjaXRlQ2VsbHNfY2l0X3NlbCA9IGNpdGVDZWxsc19jaXRbLGNpdGVDZWxsc19jaXQkQURUX3Nubl9yZXMuMS44ICVpbiUgYygiNSIsIjYiLCIxMSIpXQpEb0hlYXRtYXAoY2l0ZUNlbGxzX2NpdF9zZWwsIGFzc2F5ID0gIlNDVCIsIGZlYXR1cmVzPXRvcF9nZSRnZW5lKStOb0xlZ2VuZCgpCmBgYAoKCg==